from djmockserver.mock.jsonexception import BaseDiff, TypeDiff, LenDiff, KeyDiff, ValueDiff

import logging

logger = logging.getLogger(__name__)


class JsonCompare:
    def __init__(self):
        self._ignore_order = True # sorted list than compare
        self._result = True # 比对结果,默认相同 True
        self._diff_path = 'root' # 顶层检查出不同,则拼接root, 其他则拼接key或index, 以点连接
    
    @property
    def diffpath(self):
        "返回对比不一致的路径, 如一致,返回None"
        return self._diff_path if not self._result else None

    def _list_cmp(self, a, b, parent_path=None):
        def _get_not_match_item(params):
            try:
                self._comm_cmp(params[0], params[1])
                return False
            except BaseDiff:
                return True

        if len(a) != len(b):
            self._diff_path = parent_path or self._diff_path
            raise LenDiff(diff_path=self._diff_path)

        if self._ignore_order:
            # 忽略列表顺序,校验全部元素是否一样
            for index, x in enumerate(a):
                new_b = [item[1] for item in list(filter(_get_not_match_item, zip([x]*len(b), b)))]
                if len(new_b) == len(b):
                    # 如果长度相同,a中元素与b中元素无匹配的
                    self._diff_path = '{}.{}'.format(parent_path or self._diff_path, index)
                    raise ValueDiff(diff_path=self._diff_path)
                # 下次循环,使用当前未匹配的元素
                b = new_b
        else:
            # 按列表顺序比对元素
            for index, (e1, e2) in enumerate(list(zip(a, b))):
                self._comm_cmp(e1, e2, '{}.{}'.format(parent_path or self._diff_path, index))

    def _dict_cmp(self, a, b, parent_path=None):
        for key, value in a.items():
            if key in b:
                self._comm_cmp(value, b[key], '{}.{}'.format(parent_path or self._diff_path, key))
            else:
                self._diff_path = '{}.{}'.format(parent_path or self._diff_path, key)
                raise KeyDiff(diff_path=self._diff_path)
        for key, value in b.items():
            if key not in a:
                self._diff_path = '{}.{}'.format(parent_path or self._diff_path, key)
                raise KeyDiff(diff_path=self._diff_path)

    def _comm_cmp(self, a, b, parent_path=None):
        if type(a) != type(b):
            self._diff_path = parent_path or self._diff_path
            raise TypeDiff(diff_path=self._diff_path)

        if isinstance(a, dict):
            self._dict_cmp(a, b, parent_path=parent_path)
        elif isinstance(a, list):
            self._list_cmp(a, b, parent_path=parent_path)
        else:
            if a != b:
                self._diff_path = parent_path or self._diff_path
                raise ValueDiff(diff_path=self._diff_path)

    def compare(self, a, b, ignore_order=True):
        self._ignore_order = ignore_order
        logger.debug('data to be comparing a:{}\tb:{}'.format(a, b))
        try:
            self._comm_cmp(a, b, parent_path=self._diff_path)
            return self._result
        except BaseDiff as e:
            logger.info('matched failed : {}'.format(e))
            self._result = False
            return self._result
        except Exception as e:
            logger.info('matched exception : {}'.format(e))
            self._result = False
            return self._result

if __name__ == '__main__':
    req = [{
            "uri": "t2/",
          "method": "POST",
          "queries":{"a":1, "b":2},
          "json":[{'b':[{"b":2, "a":1}, 1,5]}, {'a':1}]
        }]
        
    req2 = {
            "uri": "t2/",
            "method": "POST",
            "json":[{'a':1},{'b':[1,{"a":1,"b":2},5]}],
            "queries":{"b":2, "a":1}
        }


    def root_type_diff():
        req = [{'a':1}]
        req2 = {'a':1}
        jc = JsonCompare()
        print(jc.compare(req,req2))
    
    root_type_diff()

    def root_value_diff():
        req = (1,2)
        req2 = (1,3)
        jc = JsonCompare()
        print(jc.compare(req,req2))
    
    root_value_diff()

    def dict_key_diff():
        req = {'a':3, 'b':4, 'e':5}
        req2 = {'a':1, 'b':2, 'f':6}
        jc = JsonCompare()
        print(jc.compare(req,req2))
        
    dict_key_diff()

    def dict_value_diff():
        req = {'a':1, 'b':{'c':3, 'b':2}}
        req2 = {'a':1, 'b':{'c':4}}
        jc = JsonCompare()
        print(jc.compare(req,req2))
        
    dict_value_diff()

    def list_len_diff():
        req = {'a':1, 'b':[1,3,4]}
        req2 = {'a':1, 'b':[1,2]}
        jc = JsonCompare()
        print(jc.compare(req,req2))
        
    list_len_diff()

    def list_order_diff():
        req = {'a':1, 'b':[1,3,4]}
        req2 = {'a':1, 'b':[1,4,3]}
        jc = JsonCompare()
        print(jc.compare(req,req2,ignore_order=False))
        
    list_order_diff()

    def list_ignore_order_diff():
        req = {'a':1, 'b':[1,3,4, [1,2]]}
        req2 = {'a':1, 'b':[1,4,3,[2,1]]}
        jc = JsonCompare()
        print(jc.compare(req,req2,ignore_order=True))

    list_ignore_order_diff()

    def dict_list_ignore_order_diff():
        req = [{'a':1}, {'b':2}, {'c':3}]
        req2 = [{'a':1}, {'c':3}, {'b':2}]
        jc = JsonCompare()
        print(jc.compare(req,req2,ignore_order=True))
        
    dict_list_ignore_order_diff()


